/**
 *    Copyright 2009-2015 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.io;

import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;

/**
 * A {@link VFS} implementation that works with the VFS API provided by JBoss 6.
 * (调用Jboss6的VFS API)
 * Jboss6VFS资源加载实现
 * 
 * @author Ben Gunter
 */
public class JBoss6VFS extends VFS {
	private static final Log log = LogFactory.getLog(ResolverUtil.class);

	/**
	 * A class that mimics a tiny subset of the JBoss VirtualFile class.(模仿JBOSS
	 * 虚拟文件类的一个小的子集类) 虚拟文件
	 */
	static class VirtualFile {
		/**
		 * 虚拟文件
		 */
		static Class<?> VirtualFile;
		/**
		 * 得到相对路径名，
		 */
		static Method getPathNameRelativeTo,
				/**
				 * 得到子类递归
				 */
				getChildrenRecursively;
		/**
		 * 虚拟文件
		 */
		Object virtualFile;

		VirtualFile(Object virtualFile) {
			this.virtualFile = virtualFile;
		}

		/**
		 * 得到相对路径名字
		 */
		String getPathNameRelativeTo(VirtualFile parent) {
			try {
				/**
				 * 反射
				 */
				return invoke(getPathNameRelativeTo, virtualFile, parent.virtualFile);
			} catch (IOException e) {
				// This exception is not thrown by the called method
				log.error("This should not be possible. VirtualFile.getPathNameRelativeTo() threw IOException.");
				return null;
			}
		}

		/**
		 * 得到子类
		 * 
		 * @return
		 * @throws IOException
		 */
		List<VirtualFile> getChildren() throws IOException {
			List<?> objects = invoke(getChildrenRecursively, virtualFile);
			List<VirtualFile> children = new ArrayList<VirtualFile>(objects.size());
			for (Object object : objects) {
				children.add(new VirtualFile(object));
			}
			return children;
		}
	}

	/**
	 * A class that mimics a tiny subset of the JBoss VFS class.(一个模仿JBOSS
	 * VFS类的小子集的类)
	 */
	static class VFS {
		static Class<?> VFS;
		static Method getChild;

		private VFS() {
			// Prevent Instantiation
		}

		/**
		 * 通过反射得到子类
		 * 
		 * @param url
		 * @return
		 * @throws IOException
		 */
		static VirtualFile getChild(URL url) throws IOException {
			Object o = invoke(getChild, VFS, url);
			return o == null ? null : new VirtualFile(o);
		}
	}

	/**
	 * Flag that indicates if this VFS is valid for the current
	 * environment.(指示此VFS是否对当前环境有效的标志)
	 */
	private static Boolean valid;

	/**
	 * 初始化 Find all the classes and methods that are required to access the
	 * JBoss 6 VFS.(查找访问JBASS 6 VFS所需的所有类和方法)
	 */
	protected static synchronized void initialize() {
		if (valid == null) {
			// Assume valid. It will get flipped later if something goes wrong.
			// 假定有效。如果出了差错，以后会翻转的
			valid = Boolean.TRUE;

			// Look up and verify required classes
			// 查找和验证必须的类集
			VFS.VFS = checkNotNull(getClass("org.jboss.vfs.VFS"));
			VirtualFile.VirtualFile = checkNotNull(getClass("org.jboss.vfs.VirtualFile"));

			// Look up and verify required methods(查找和验证必须的方法)
			VFS.getChild = checkNotNull(getMethod(VFS.VFS, "getChild", URL.class));
			VirtualFile.getChildrenRecursively = checkNotNull(
					getMethod(VirtualFile.VirtualFile, "getChildrenRecursively"));
			VirtualFile.getPathNameRelativeTo = checkNotNull(
					getMethod(VirtualFile.VirtualFile, "getPathNameRelativeTo", VirtualFile.VirtualFile));

			// Verify that the API has not changed(检查这个api是否发生改变)
			checkReturnType(VFS.getChild, VirtualFile.VirtualFile);
			checkReturnType(VirtualFile.getChildrenRecursively, List.class);
			checkReturnType(VirtualFile.getPathNameRelativeTo, String.class);
		}
	}

	/**
	 * Verifies that the provided object reference is null. If it is null, then
	 * this VFS is marked as invalid for the current
	 * environment.(验证所提供的对象引用是否为空。如果为null，则当前环境下将此VFS标记为无效。)
	 * 
	 * @param object
	 *            The object reference to check for null.
	 */
	protected static <T> T checkNotNull(T object) {
		if (object == null) {
			setInvalid();
		}
		return object;
	}

	/**
	 * Verifies that the return type of a method is what it is expected to be.
	 * If it is not, then this VFS is marked as invalid for the current
	 * environment.(验证方法的返回类型是否是预期的。如果不是，则此VFS被标记为当前环境无效)
	 * 
	 * @param method
	 *            The method whose return type is to be checked.
	 * @param expected
	 *            A type to which the method's return type must be assignable.
	 * @see Class#isAssignableFrom(Class)
	 */
	protected static void checkReturnType(Method method, Class<?> expected) {
		if (method != null && !expected.isAssignableFrom(method.getReturnType())) {
			log.error("Method " + method.getClass().getName() + "." + method.getName() + "(..) should return "
					+ expected.getName() + " but returns " + method.getReturnType().getName() + " instead.");
			setInvalid();
		}
	}

	/** Mark this {@link VFS} as invalid for the current environment. 
	 *  将此标记为当前环境无效
	 */
	protected static void setInvalid() {
		if (JBoss6VFS.valid == Boolean.TRUE) {
			log.debug("JBoss 6 VFS API is not available in this environment.");
			JBoss6VFS.valid = Boolean.FALSE;
		}
	}
	/**
	 * 类装载的时候被装载到内存
	 */
	static {
		initialize();
	}

	@Override
	public boolean isValid() {
		return valid;
	}

	@Override
	public List<String> list(URL url, String path) throws IOException {
		VirtualFile directory;
		directory = VFS.getChild(url);
		if (directory == null) {
			return Collections.emptyList();
		}

		if (!path.endsWith("/")) {
			path += "/";
		}

		List<VirtualFile> children = directory.getChildren();
		List<String> names = new ArrayList<String>(children.size());
		for (VirtualFile vf : children) {
			names.add(path + vf.getPathNameRelativeTo(directory));
		}

		return names;
	}
}
